<!DOCTYPE html>
<html lang="zhCN">
    <head>
        <title>羊了个羊3D地图</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
            body {
                background-color: #cff998;
                color: #000;
                overflow: hidden;
                font-family: Monospace;
                font-size: 13px;
                line-height: 24px;
                overscroll-behavior: none;
            }
            a {
                color: #f00;
                text-decoration: none;
            }
            a:hover {
                text-decoration: underline;
            }
            * {
                margin:0;
            }
            #info {
                position: absolute;
                top: 0px;
                width: 100%;
                padding: 10px;
                box-sizing: border-box;
                text-align: center;
                -moz-user-select: none;
                -webkit-user-select: none;
                -ms-user-select: none;
            }
        </style>
    </head>

    <body>
        <div id="info">
            <a id="undo" href="javascript:void(0)">【撤销移除】</a>
            <a id="auto_solve" href="javascript:void(0)"></a>
            <a id="single_step_solve" href="javascript:void(0)"></a>
        </div>

        <!-- Import maps polyfill -->
        <!-- Remove this when import maps will be widely supported -->
        <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>

        <script type="importmap">
            {
                "imports": {
                    "three": "https://unpkg.com/three@0.145.0/build/three.module.js",
                    "OrbitControls": "https://unpkg.com/three@0.145.0/examples/jsm/controls/OrbitControls.js"
                }
            }
        </script>

        <script type="module">

            import * as THREE from 'three';

            import { OrbitControls } from 'OrbitControls';

            let camera, controls, scene, renderer;
            const raycaster = new THREE.Raycaster();
            const mouse = new THREE.Vector2();
            const removed_blocks = [];
            var map_data;

            // 显示解答步骤的变量
            var block_objects = {}
            var solve_index = 0;
            var highlight_mesh = null;
            var solve_interval = null;

            var material_blocks = [];
            var material_side;

            // 卡槽
            var slots = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
            var counter = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
            var slot_objects = [];

            init();
            //render(); // remove when using next line for animation loop (requestAnimationFrame)
            animate();

            bindEvent();

            function init() {

                scene = new THREE.Scene();
                scene.background = new THREE.Color( 0xcff998 );
                //scene.fog = new THREE.FogExp2( 0xcccccc, 0.002 );

                renderer = new THREE.WebGLRenderer( { antialias: true } );
                renderer.setPixelRatio( window.devicePixelRatio );
                renderer.setSize( window.innerWidth, window.innerHeight );
                document.body.appendChild( renderer.domElement );

                //camera = new THREE.PerspectiveCamera( 90, window.innerWidth / window.innerHeight, 1, 2000 );
                camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 2000 );
                camera.zoom = 6;
                camera.setViewOffset( window.innerWidth, window.innerHeight, 0, -150, window.innerWidth, window.innerHeight );
                camera.position.set( 0, 300, 0 );

                // controls
                controls = new OrbitControls( camera, renderer.domElement );
                controls.listenToKeyEvents( window ); // optional
                //controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
                controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
                controls.dampingFactor = 0.05;
                controls.screenSpacePanning = false;
                controls.minDistance = 100;
                controls.maxDistance = 500;
                controls.maxPolarAngle = Math.PI / 2;

                // 加载地图数据
                var url = "./map_data.json"
                var request = new XMLHttpRequest();
                request.open("get", url);
                request.send(null);
                request.onload = function () {
                    if (request.status == 200) {
                        try {
                            map_data = JSON.parse(request.responseText);
                            render_map();
                        } catch(e) {
                            console.log(e);
                            alert("解析地图数据出错");
                        }
                    } else {
                        alert("获取地图数据出错");
                    }
                }
            }

            function render_map() {

                // 构建地图遮挡关系
                var mask_data = [];
                for (var row = 0; row < 80; row++) {
                    var rows = [];
                    for (var col = 0; col < 64; col++) {
                        rows.push(null);
                    }
                    mask_data.push(rows);
                }
                const level_data = map_data['levelData'];
                var layers = Object.keys(level_data);
                layers.sort(function(a, b) {
                    return parseInt(a) - parseInt(b);
                })
                for (let i = 0; i < layers.length; i++) {
                    var layer = layers[i];
                    var block_datas = level_data[layer];
                    for (let j = 0; j < block_datas.length; j++) {
                        var block_data = block_datas[j];
                        counter[block_data.type] += 1;
                        block_data.pre_blocks = [];     // 遮挡住的方块
                        block_data.next_blocks = [];    // 被遮挡的方块
                        // 取走方块方法
                        block_data.take_away = function() {
                            for (var i = 0; i < this.pre_blocks.length; i++) {
                                var pre_block = this.pre_blocks[i];
                                pre_block.next_blocks.pop(this);
                                update_block_material(block_objects[pre_block.id]);
                            }
                            counter[this.type] -= 1;
                            slots[this.type] += 1;
                            if (slots[this.type] >= 3) {
                                slots[this.type] = 0;
                            }
                            update_slots();
                        }
                        // 放回方块方法
                        block_data.put_back = function() {
                            for (var i = 0; i < this.pre_blocks.length; i++) {
                                var pre_block = this.pre_blocks[i];
                                pre_block.next_blocks.push(this);
                                update_block_material(block_objects[pre_block.id]);
                            }
                            counter[this.type] += 1;
                            slots[this.type] -= 1;
                            if (slots[this.type] < 0) {
                                slots[this.type] = 2;
                            }
                            update_slots();
                        }
                        // 是否可以移除
                        block_data.removable = function() {
                            return this.next_blocks.length == 0;
                        }
                        // 找出被当前方块遮挡住的方块
                        var pre_blocks = [];
                        for (var col = block_data.rolNum; col < block_data.rolNum+8; col++) {
                            for (var row = block_data.rowNum; row < block_data.rowNum+8; row++) {
                                var pre_block = mask_data[row][col];
                                if (pre_block) {
                                    pre_blocks.push(pre_block);
                                }
                                mask_data[row][col] = block_data;
                            }
                        }
                        // 构建方块的遮挡关系
                        for (var k = 0; k < pre_blocks.length; k++) {
                            var pre_block = pre_blocks[k];
                            if (block_data.pre_blocks.indexOf(pre_block) < 0) {
                                block_data.pre_blocks.push(pre_block);
                            }
                            if (pre_block.next_blocks.indexOf(block_data) < 0) {
                                pre_block.next_blocks.push(block_data);
                            }
                        }
                    }
                }
                //console.log(mask_data);

                // 方块图案
                const textureLoader = new THREE.TextureLoader();
                material_side = new THREE.MeshLambertMaterial({
                    map: textureLoader.load('images/side.png'),
                })
                for (let i = 0; i <= 16; i++) {
                    material_blocks.push(new THREE.MeshLambertMaterial({
                        map: textureLoader.load(`images/${i}.png`),
                    }));
                }

                // 创建方块
                var geometry = new THREE.BoxGeometry(8, 2, 8);
                for (let i = 0; i < layers.length; i++) {
                    var layer = layers[i];
                    var block_datas = level_data[layer];
                    for (let j = 0; j < block_datas.length; j++) {
                        var block_data = block_datas[j];
                        print_block_info(block_data, false);

                        // 方块
                        const block_object = new THREE.Mesh(geometry, null);
                        block_object.position.x = block_data['rolNum'] - 28;
                        block_object.position.y = (block_data['layerNum'] - 1) * 3.5;
                        block_object.position.z = block_data['rowNum'] - 36;
                        block_object.updateMatrix();
                        block_object.matrixAutoUpdate = false;
                        block_object.block_data = block_data;
                        scene.add(block_object);
                        block_objects[block_data['id']] = block_object;

                        // 遮挡阴影
                        const mask_geometry = new THREE.BoxGeometry(7.999, 1.999, 7.999);
                        const mask_material = new THREE.MeshBasicMaterial({color:0x000000, opacity:0.5, transparent:true});
                        const mask_object = new THREE.Mesh(mask_geometry, mask_material);
                        mask_object.position.y = 0.001;
                        mask_object.is_mask = true;
                        block_object.mask_object = mask_object;
                        update_block_material(block_object);
                    }
                }

                // 卡槽方块
                for (let i = 0; i < 7; i++) {
                    const block_object = new THREE.Mesh(geometry, null);
                    block_object.position.x = 8 * i - 24;
                    block_object.position.y = 0;
                    block_object.position.z = -56;
                    block_object.updateMatrix();
                    block_object.matrixAutoUpdate = false;
                    scene.add(block_object);
                    slot_objects.push(block_object);
                    var block_data = {
                        "type" : 0,
                        "pre_blocks" : [],
                        "next_blocks" : [],
                        "is_slot" : true,
                    }
                    block_data.removable = function() {
                        return true;
                    }
                    block_object.block_data = block_data;
                    update_block_material(block_object);
                }

                // 如果有解题步骤
                if (has_solve_steps()) {
                    // 添加高亮指示器
                    geometry = new THREE.BoxGeometry(8, 2, 8);
                    const material = new THREE.MeshBasicMaterial({color:0xff0000, opacity:0.6, transparent:true})
                    highlight_mesh = new THREE.Mesh(geometry, material);
                    highlight_mesh.position.x = 1000;
                    highlight_mesh.position.y = 1000;
                    highlight_mesh.position.z = 1000;
                    highlight_mesh.updateMatrix();
                    highlight_mesh.matrixAutoUpdate = false;
                    scene.add(highlight_mesh);
                    update_highlight_mesh();

                    // 显示解题按钮
                    document.getElementById('auto_solve').text = "【自动解答】";
                    document.getElementById('single_step_solve').text = "【单步解答】";
                }

                // lights
                const dirLight0 = new THREE.DirectionalLight( 0xffffff );
                dirLight0.position.set( 0, 100, 0 );
                scene.add( dirLight0 );

                const dirLight1 = new THREE.DirectionalLight( 0xffffff );
                dirLight1.position.set( 1, 0, 1 );
                scene.add( dirLight1 );

                const dirLight2 = new THREE.DirectionalLight( 0xffffff );
                dirLight2.position.set( - 1, - 1, - 1 );
                scene.add( dirLight2 );

                const ambientLight = new THREE.AmbientLight( 0x222222 );
                scene.add( ambientLight );

                window.addEventListener( 'resize', onWindowResize );

                document.addEventListener( 'click', onMouseClick );
            }

            function onWindowResize() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize( window.innerWidth, window.innerHeight );
            }

            function onMouseClick(event) {

                //将鼠标点击位置的屏幕坐标转换成threejs中的标准坐标
                mouse.x = (event.clientX/window.innerWidth) * 2 - 1;
                mouse.y = -((event.clientY/window.innerHeight) * 2 - 1);
             
                // 通过鼠标点的位置和当前相机的矩阵计算出raycaster
                raycaster.setFromCamera( mouse, camera );
             
                // 获取raycaster直线和所有模型相交的数组集合
                var intersects = raycaster.intersectObjects( scene.children );
                //console.log(intersects);
             
                for (var i = 0; i < intersects.length; i++) {
                    var object = intersects[i].object;
                    if (object.is_mask) {
                        continue;
                    }
                    var block_data = object.block_data;
                    if (!block_data.removable()) {
                        return;
                    }
                    if (block_data.is_slot) {
                        return;
                    }
                    if (has_solve_steps()) {
                        alert("有解的情况禁止手动移除方块，不然把解答途中的方块移除掉会导致局面出现问题。");
                        return;
                    }
                    if (is_game_over()) {
                        return;
                    }
                    print_block_info(block_data, true);
                    removed_blocks.push(object);
                    scene.remove(object);
                    block_data.take_away();
                    return;
                }
            }

            function animate() {
                requestAnimationFrame( animate );
                controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
                render();
            }

            function render() {
                renderer.render( scene, camera );
            }

            // 打印方块信息
            function print_block_info(block_data, is_remove) {

                var type2name = {
                    1:  "青草",
                    2:  "胡萝卜",
                    3:  "玉米",
                    4:  "树桩",
                    5:  "草叉",
                    6:  "白菜",
                    7:  "羊毛",
                    8:  "刷子",
                    9:  "剪刀",
                    10: "奶瓶",
                    11: "水桶",
                    12: "手套",
                    13: "铃铛",
                    14: "火堆",
                    15: "毛球",
                    16: "干草"
                }

                var layer = block_data['layerNum'];
                var x = block_data['rolNum'];
                var y = block_data['rowNum'];
                var type = block_data['type'];
                var name = type2name[type];
                var info = `层数:${layer}, 坐标:(${x}, ${y}), 类型:${type}(${name})`
                if (is_remove) {
                    info = "移除方块 >> " + info;
                }
                console.log(info);
            }

            // 游戏是否结束
            function is_game_over() {
                var sum = 0;
                for (var i = 0; i < slots.length; i++) {
                    sum += slots[i];
                }
                return sum >= 7;
            }

            // 更新方块图案
            function update_block_material(block_object) {
                // 更新图案
                var block_data = block_object.block_data; 
                var material = material_blocks[block_data.type];
                var materials = block_object.material;
                if (materials == null) {
                    materials = [material_side, material_side, material, material_side, material_side, material_side];
                } else {
                    materials[2] = material;
                }
                block_object.material = materials;

                // 更新遮挡阴影
                if (block_data.removable()) {
                    block_object.remove(block_object.mask_object);
                } else {
                    block_object.add(block_object.mask_object);
                }
            }

            // 更新卡槽方块
            function update_slots() {
                for (let i = 0; i < 7; i++) {
                    var block_object = slot_objects[i];
                    block_object.block_data.type = 0;
                    update_block_material(block_object);
                }

                var index = 0;
                for (let i = 0; i < slots.length; i++) {
                    for (let j = 0; j < slots[i]; j++) {
                        if (index >= 7) {
                            return;
                        }
                        var block_object = slot_objects[index];
                        block_object.block_data.type = i;
                        update_block_material(block_object);
                        index += 1;
                    }
                }
            }

            // 是否包含解题步骤
            function has_solve_steps() {
                if (map_data['operations'] == null) {
                    return false;
                }
                if (map_data['operations'].length == 0) {
                    return false;
                }
                return true;
            }

            // 更新高亮方块位置
            function update_highlight_mesh() {
                if (!has_solve_steps()) {
                    return;
                }
                if (solve_index < map_data['operations'].length) {
                    var block_id = map_data['operations'][solve_index];
                    var block_object = block_objects[block_id];
                    highlight_mesh.position.x = block_object.position.x;
                    highlight_mesh.position.y = block_object.position.y;
                    highlight_mesh.position.z = block_object.position.z;
                } else {
                    highlight_mesh.position.x = 1000;
                    highlight_mesh.position.y = 1000;
                    highlight_mesh.position.z = 1000;
                }
                highlight_mesh.updateMatrix();
            }

            // 撤销移除
            function undo() {
                const object = removed_blocks.pop();
                if (object) {
                    scene.add(object);
                    object.block_data.put_back();
                    if (solve_index > 0) {
                        solve_index -= 1;
                    }
                    update_highlight_mesh();
                }
            }

            // 自动解答
            function auto_solve() {
                var auto_solve_element = document.getElementById('auto_solve');
                if (solve_interval == null) {
                    solve_interval = setInterval(function() {
                        single_step_solve();
                    }, 1500);
                    auto_solve_element.text = '【停止解答】';
                } else {
                    clearInterval(solve_interval);
                    solve_interval = null;
                    auto_solve_element.text = '【自动解答】';
                }
            }

            // 单步解答
            function single_step_solve() {
                if (!has_solve_steps()) {
                    return;
                }
                if (solve_index >= map_data['operations'].length) {
                    return;
                }
                var block_id = map_data['operations'][solve_index];
                var block_object = block_objects[block_id];
                scene.remove(block_object);
                removed_blocks.push(block_object);
                print_block_info(block_object.block_data, true);
                block_object.block_data.take_away();
                solve_index += 1;
                update_highlight_mesh();
                //console.log("counts :", counter);
                //console.log("slots  :", slots);
            }

            function bindEvent(){
                document.getElementById('undo').onclick = undo;
                document.getElementById('auto_solve').onclick = auto_solve;
                document.getElementById('single_step_solve').onclick = single_step_solve;
            }

        </script>

    </body>
</html>
